package com.cencat.common.utils;

import com.cencat.common.constants.CommonConstants;
import com.cencat.common.enums.ErrorCode;
import com.cencat.common.exception.BusinessException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.security.Keys;
import lombok.experimental.UtilityClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.SecretKey;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * JWT工具类
 * 提供JWT令牌的生成、解析和验证功能
 * 
 * @author cencat
 * @since 2024-01-01
 */
@UtilityClass
public class JwtUtils {
    
    private static final Logger log = LoggerFactory.getLogger(JwtUtils.class);

    /**
     * JWT密钥
     */
    private static final String JWT_SECRET = "cencat-saas-jwt-secret-key-2024-very-long-and-secure";

    /**
     * JWT签名密钥
     */
    private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor(JWT_SECRET.getBytes());

    /**
     * 默认过期时间（7天）
     */
    private static final long DEFAULT_EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000L;

    /**
     * 刷新令牌过期时间（30天）
     */
    private static final long REFRESH_EXPIRE_TIME = 30 * 24 * 60 * 60 * 1000L;

    /**
     * 令牌类型
     */
    public static final String TOKEN_TYPE_ACCESS = "access";
    public static final String TOKEN_TYPE_REFRESH = "refresh";

    /**
     * Claims键名
     */
    public static final String CLAIM_USER_ID = "userId";
    public static final String CLAIM_USERNAME = "username";
    public static final String CLAIM_TENANT_ID = "tenantId";
    public static final String CLAIM_ROLES = "roles";
    public static final String CLAIM_TOKEN_TYPE = "tokenType";

    /**
     * 生成访问令牌
     * 
     * @param userId 用户ID
     * @param username 用户名
     * @param tenantId 租户ID
     * @param roles 用户角色
     * @return JWT令牌
     */
    public static String generateAccessToken(Long userId, String username, Long tenantId, String roles) {
        return generateToken(userId, username, tenantId, roles, TOKEN_TYPE_ACCESS, DEFAULT_EXPIRE_TIME);
    }

    /**
     * 生成刷新令牌
     * 
     * @param userId 用户ID
     * @param username 用户名
     * @param tenantId 租户ID
     * @param roles 用户角色
     * @return JWT刷新令牌
     */
    public static String generateRefreshToken(Long userId, String username, Long tenantId, String roles) {
        return generateToken(userId, username, tenantId, roles, TOKEN_TYPE_REFRESH, REFRESH_EXPIRE_TIME);
    }

    /**
     * 生成JWT令牌
     * 
     * @param userId 用户ID
     * @param username 用户名
     * @param tenantId 租户ID
     * @param roles 用户角色
     * @param tokenType 令牌类型
     * @param expireTime 过期时间（毫秒）
     * @return JWT令牌
     */
    public static String generateToken(Long userId, String username, Long tenantId, String roles, 
                                      String tokenType, long expireTime) {
        try {
            Date now = new Date();
            Date expiration = new Date(now.getTime() + expireTime);

            Map<String, Object> claims = new HashMap<>();
            claims.put(CLAIM_USER_ID, userId);
            claims.put(CLAIM_USERNAME, username);
            claims.put(CLAIM_TENANT_ID, tenantId);
            claims.put(CLAIM_ROLES, roles);
            claims.put(CLAIM_TOKEN_TYPE, tokenType);

            return Jwts.builder()
                    .claims(claims)
                    .subject(username)
                    .issuedAt(now)
                    .expiration(expiration)
                    .signWith(SECRET_KEY)
                    .compact();

        } catch (Exception e) {
            log.error("生成JWT令牌失败: userId={}, username={}, error={}", userId, username, e.getMessage(), e);
            throw new BusinessException(ErrorCode.OPERATION_FAILED, "生成令牌失败");
        }
    }

    /**
     * 解析JWT令牌
     * 
     * @param token JWT令牌
     * @return 令牌信息
     * @throws BusinessException 解析失败异常
     */
    public static JwtTokenInfo parseToken(String token) throws BusinessException {
        if (CencatStringUtils.isBlank(token)) {
            throw new BusinessException(ErrorCode.UNAUTHORIZED, "令牌不能为空");
        }

        // 移除Bearer前缀
        if (token.startsWith(CommonConstants.TOKEN_PREFIX)) {
            token = token.substring(CommonConstants.TOKEN_PREFIX.length());
        }

        try {
            Claims claims = Jwts.parser()
                    .verifyWith(SECRET_KEY)
                    .build()
                    .parseSignedClaims(token)
                    .getPayload();

            JwtTokenInfo tokenInfo = new JwtTokenInfo();
            tokenInfo.userId = claims.get(CLAIM_USER_ID, Long.class);
            tokenInfo.username = claims.get(CLAIM_USERNAME, String.class);
            tokenInfo.tenantId = claims.get(CLAIM_TENANT_ID, Long.class);
            tokenInfo.roles = claims.get(CLAIM_ROLES, String.class);
            tokenInfo.tokenType = claims.get(CLAIM_TOKEN_TYPE, String.class);
            tokenInfo.subject = claims.getSubject();
            tokenInfo.issuedAt = claims.getIssuedAt();
            tokenInfo.expiration = claims.getExpiration();
            tokenInfo.expired = isTokenExpired(claims.getExpiration());

            return tokenInfo;

        } catch (ExpiredJwtException e) {
            log.warn("JWT令牌已过期: {}", e.getMessage());
            throw new BusinessException(ErrorCode.TOKEN_EXPIRED, "令牌已过期");
        } catch (UnsupportedJwtException e) {
            log.warn("不支持的JWT令牌: {}", e.getMessage());
            throw new BusinessException(ErrorCode.UNAUTHORIZED, "不支持的令牌格式");
        } catch (MalformedJwtException e) {
            log.warn("JWT令牌格式错误: {}", e.getMessage());
            throw new BusinessException(ErrorCode.UNAUTHORIZED, "令牌格式错误");
        } catch (SecurityException | IllegalArgumentException e) {
            log.warn("JWT令牌验证失败: {}", e.getMessage());
            throw new BusinessException(ErrorCode.UNAUTHORIZED, "令牌验证失败");
        } catch (Exception e) {
            log.error("解析JWT令牌异常: {}", e.getMessage(), e);
            throw new BusinessException(ErrorCode.UNAUTHORIZED, "令牌解析失败");
        }
    }

    /**
     * 验证JWT令牌
     * 
     * @param token JWT令牌
     * @return 是否有效
     */
    public static boolean validateToken(String token) {
        try {
            parseToken(token);
            return true;
        } catch (BusinessException e) {
            return false;
        }
    }

    /**
     * 检查令牌是否过期
     * 
     * @param expiration 过期时间
     * @return 是否过期
     */
    public static boolean isTokenExpired(Date expiration) {
        return expiration.before(new Date());
    }

    /**
     * 获取令牌剩余有效时间（秒）
     * 
     * @param token JWT令牌
     * @return 剩余有效时间（秒），-1表示已过期
     */
    public static long getTokenRemainingTime(String token) {
        try {
            JwtTokenInfo tokenInfo = parseToken(token);
            if (tokenInfo.expired) {
                return -1;
            }
            
            long currentTime = System.currentTimeMillis();
            long expirationTime = tokenInfo.expiration.getTime();
            return (expirationTime - currentTime) / 1000;
            
        } catch (BusinessException e) {
            return -1;
        }
    }

    /**
     * 刷新访问令牌
     * 
     * @param refreshToken 刷新令牌
     * @return 新的访问令牌
     * @throws BusinessException 刷新失败异常
     */
    public static String refreshAccessToken(String refreshToken) throws BusinessException {
        JwtTokenInfo tokenInfo = parseToken(refreshToken);
        
        // 验证是否为刷新令牌
        if (!TOKEN_TYPE_REFRESH.equals(tokenInfo.tokenType)) {
            throw new BusinessException(ErrorCode.UNAUTHORIZED, "无效的刷新令牌");
        }
        
        // 验证令牌是否过期
        if (tokenInfo.expired) {
            throw new BusinessException(ErrorCode.TOKEN_EXPIRED, "刷新令牌已过期");
        }
        
        // 生成新的访问令牌
        return generateAccessToken(
                tokenInfo.userId,
                tokenInfo.username,
                tokenInfo.tenantId,
                tokenInfo.roles
        );
    }

    /**
     * 从令牌中提取用户ID
     * 
     * @param token JWT令牌
     * @return 用户ID
     */
    public static Long extractUserId(String token) {
        try {
            JwtTokenInfo tokenInfo = parseToken(token);
            return tokenInfo.userId;
        } catch (BusinessException e) {
            log.warn("提取用户ID失败: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 从令牌中提取租户ID
     * 
     * @param token JWT令牌
     * @return 租户ID
     */
    public static Long extractTenantId(String token) {
        try {
            JwtTokenInfo tokenInfo = parseToken(token);
            return tokenInfo.tenantId;
        } catch (BusinessException e) {
            log.warn("提取租户ID失败: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 从令牌中提取用户名
     * 
     * @param token JWT令牌
     * @return 用户名
     */
    public static String extractUsername(String token) {
        try {
            JwtTokenInfo tokenInfo = parseToken(token);
            return tokenInfo.username;
        } catch (BusinessException e) {
            log.warn("提取用户名失败: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 生成令牌对（访问令牌 + 刷新令牌）
     * 
     * @param userId 用户ID
     * @param username 用户名
     * @param tenantId 租户ID
     * @param roles 用户角色
     * @return 令牌对
     */
    public static TokenPair generateTokenPair(Long userId, String username, Long tenantId, String roles) {
        String accessToken = generateAccessToken(userId, username, tenantId, roles);
        String refreshToken = generateRefreshToken(userId, username, tenantId, roles);
        
        TokenPair tokenPair = new TokenPair();
        tokenPair.accessToken = accessToken;
        tokenPair.refreshToken = refreshToken;
        tokenPair.tokenType = "Bearer";
        tokenPair.expiresIn = DEFAULT_EXPIRE_TIME / 1000; // 转换为秒
        
        return tokenPair;
    }

    /**
     * JWT令牌信息
     */
    public static class JwtTokenInfo {
        /**
         * 用户ID
         */
        private Long userId;

        /**
         * 用户名
         */
        private String username;

        /**
         * 租户ID
         */
        private Long tenantId;

        /**
         * 用户角色
         */
        private String roles;

        /**
         * 令牌类型
         */
        private String tokenType;

        /**
         * 主题
         */
        private String subject;

        /**
         * 签发时间
         */
        private Date issuedAt;

        /**
         * 过期时间
         */
        private Date expiration;

        /**
         * 是否过期
         */
        private boolean expired;

        /**
         * 获取签发时间（LocalDateTime）
         */
        public LocalDateTime getIssuedAtLocalDateTime() {
            return issuedAt == null ? null : 
                    LocalDateTime.ofInstant(issuedAt.toInstant(), ZoneId.systemDefault());
        }

        /**
         * 获取过期时间（LocalDateTime）
         */
        public LocalDateTime getExpirationLocalDateTime() {
            return expiration == null ? null : 
                    LocalDateTime.ofInstant(expiration.toInstant(), ZoneId.systemDefault());
        }

        /**
         * 获取剩余有效时间（秒）
         */
        public long getRemainingTimeSeconds() {
            if (expired || expiration == null) {
                return 0;
            }
            
            long currentTime = System.currentTimeMillis();
            long expirationTime = expiration.getTime();
            return Math.max(0, (expirationTime - currentTime) / 1000);
        }

        // Getter and Setter methods
        public Long getUserId() {
            return userId;
        }

        public void setUserId(Long userId) {
            this.userId = userId;
        }

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public Long getTenantId() {
            return tenantId;
        }

        public void setTenantId(Long tenantId) {
            this.tenantId = tenantId;
        }

        public String getRoles() {
            return roles;
        }

        public void setRoles(String roles) {
            this.roles = roles;
        }

        public String getTokenType() {
            return tokenType;
        }

        public void setTokenType(String tokenType) {
            this.tokenType = tokenType;
        }

        public String getSubject() {
            return subject;
        }

        public void setSubject(String subject) {
            this.subject = subject;
        }

        public Date getIssuedAt() {
            return issuedAt;
        }

        public void setIssuedAt(Date issuedAt) {
            this.issuedAt = issuedAt;
        }

        public Date getExpiration() {
            return expiration;
        }

        public void setExpiration(Date expiration) {
            this.expiration = expiration;
        }

        public boolean isExpired() {
            return expired;
        }

        public void setExpired(boolean expired) {
            this.expired = expired;
        }
    }

    /**
     * 令牌对
     */
    public static class TokenPair {
        /**
         * 访问令牌
         */
        private String accessToken;

        /**
         * 刷新令牌
         */
        private String refreshToken;

        /**
         * 令牌类型
         */
        private String tokenType;

        /**
         * 过期时间（秒）
         */
        private Long expiresIn;

        // Getter and Setter methods
        public String getAccessToken() {
            return accessToken;
        }

        public void setAccessToken(String accessToken) {
            this.accessToken = accessToken;
        }

        public String getRefreshToken() {
            return refreshToken;
        }

        public void setRefreshToken(String refreshToken) {
            this.refreshToken = refreshToken;
        }

        public String getTokenType() {
            return tokenType;
        }

        public void setTokenType(String tokenType) {
            this.tokenType = tokenType;
        }

        public Long getExpiresIn() {
            return expiresIn;
        }

        public void setExpiresIn(Long expiresIn) {
            this.expiresIn = expiresIn;
        }
    }
}